package com.ruixi.spring.aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.util.Arrays;

/**
 * AOP
 *  (1)面向切面编程,利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高开发效率
 *  (2)通俗描述: 不通过修改源代码的方式,在主干功能中添加新功能
 *
 *  切入点表达式
 *      (1)切入点表达式的作用: 确定对哪个类里面的哪个方法进行增强
 *      (2)语法结构:  execution([权限修饰符][返回类型][类全路径][方法名称]([参数列表]))
 *          权限修饰符包括public、private等,但一般为了方便,可以直接写 * ,代表任意权限修饰符
 *          返回类型可以省略
 *          参数列表可以使用 .. 来表示
 *      示例
 *          对com.suda.aop.testaop类里面的add方法进行增强
 *              execution(* com.suda.aop.testaop.add(..))
 *          对com.suda.aop.testaop类里面的所有方法进行增强
 *              execution(* com.suda.aop.testaop.*(..))
 *          对com.suda.aop包里面的所有类的所有方法进行增强
 *              execution(* com.suda.aop.*.*(..))
 */

//切面
@Aspect //该注解用于标注当前类为切面,单纯的加上该注解与Spring不能产生关联,需要将其作为Spring的组件,所以需要添加@Component注解并开启注解扫描
@Order(100)
@Component
public class MyLoggerAspect {

    //公共切入点,在下面的通知中进行引用
    //切入点的方法体通常是空的,因为将切入点定义与应用程序逻辑混在一起是不合理的
    @Pointcut(value = "execution(* com.ruixi.spring.aop.*.*(..))")
    public void test(){}

    //前置通知:在方法执行之前加入的功能
    //@Before注解用于将方法指定为前置通知
    //@Before(value = "execution(public int com.ruixi.spring.aop.MathImpl.add(int,int))") //必须设置value,其值为切入点表达式,切入点需要定位到具体的方法,所以需要修饰符,返回值,完整的方法名,参数等
    //@Before(value = "execution(public int com.ruixi.spring.aop.MathImpl.*(int,int))")

    //权限修饰符和返回值共用一个*,不能使用两个,代表不管权限修饰符和返回值
    //@Before(value = "execution(* com.ruixi.spring.aop.MathImpl.*(int,int))")

    //如果需要匹配所有的形参列表,则需要使用 ..
    //@Before(value = "execution(* com.ruixi.spring.aop.*.*(..))") //任意的类名、任意的方法名、任意的形参列表
    @Before(value = "test()") //引用公共切入点
    public void beforeMethod(JoinPoint joinPoint){ //连接点对象,当Spring解析了切入点之后,在执行前置通知时,就会将方法的一些信息封装到joinPoint中

        //获取目标方法的参数
        Object[] args = joinPoint.getArgs();

        //获取签名:对当前方法名的一些封装
        Signature signature = joinPoint.getSignature();
        //获取方法名
        String methodName = signature.getName();

        System.out.println("method:" + methodName + ",arguments:" + Arrays.toString(args));
    }

    //后置通知:在方法执行之后加入的功能,该后置通知不管是否有异常都会执行,相当于放在finally中的语句
    @After(value = "execution(* com.ruixi.spring.aop.*.*(..))")
    public void afterMethod(){
        System.out.println("后置通知");
    }

    //返回通知:在方法执行之后加入的功能,该返回通知如果发生了异常则不会执行,相当于放在了方法执行之后,所以可以设置返回值
    @AfterReturning(value = "execution(* com.ruixi.spring.aop.*.*(..))",returning = "result")//result含义:当返回通知作用到连接点之后,即方法执行之后,会将方法执行的结果赋值给一个Object类型的result,所以该result相当于接收方法返回值的一个变量名,要保证result与形参的result相同,否则不能赋值成功
    public void afterReturningMethod(JoinPoint joinPoint,Object result){
        String methodName = joinPoint.getSignature().getName();
        System.out.println("method:" + methodName + ",result:" + result);
    }

    //异常通知
    //在参数列表中可以通过设置形参的类型来设置当作用的功能抛出什么样的异常时才执行异常通知,如果抛出的不是符合的异常,则不执行该异常通知,即只处理指定的异常
    @AfterThrowing(value = "execution(* com.ruixi.spring.aop.*.*(..))",throwing = "ex")//接收当前作用到的功能所抛出的异常,原理与上面相同
    public void afterThrowingMethod(NullPointerException ex){
        System.out.println("有异常了,messages:" + ex);
    }

    //环绕通知:可以控制方法什么时候执行
    //@Around(value = "execution(* com.ruixi.spring.aop.*.*(..))")
    public Object aroundMethod(ProceedingJoinPoint joinPoint){
        Object result = null;
        try {
            //前置通知
            System.out.println("前置通知");

            //为了保证方法的一致性,需要设置返回值,因为目标方法是有返回值,所以代理对象也必须有返回值
            result = joinPoint.proceed();//执行方法,相当于代理模式中的method.invoke()方法

            //返回通知
            System.out.println("返回通知");

            //返回结果
            return result;
        } catch (Throwable throwable) {
            //异常通知
            System.out.println("异常通知");
            throwable.printStackTrace();
        }finally {
            //后置通知
            System.out.println("后置通知");
        }

        //不能设置为null,因为null无法给int赋值
        return  -1;
    }

}
